/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.webapp.security;

import org.apache.commons.lang3.StringUtils;
import org.jrose.core.util.UserPassword;
import org.jrose.entity.constant.UserType;
import org.jrose.entity.data.User;
import org.jrose.service.loader.UserLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import static org.jrose.entity.constant.UserType.ADMINISTRATOR;
import static org.jrose.entity.constant.UserType.PLAYER;

public class SecurityFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(SecurityFilter.class);

    private final static Map<String, Set<UserType>> permissions;

    static {
        permissions = new LinkedHashMap<>();
        permissions.put("admin/", new LinkedHashSet<>(Arrays.asList(ADMINISTRATOR)));
        permissions.put("home/", new LinkedHashSet<>(Arrays.asList(PLAYER)));
    }

    private UserLoader userLoader;

    @Override
    public void init(FilterConfig config) throws ServletException {
        ServletContext servletContext = config.getServletContext();
        WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        userLoader = applicationContext.getBean(UserLoader.class);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) {
            log.warn("HttpServletRequest and HttpServletResponse are expected");
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        SecurityContext context = new SecurityContext(request, response);

        try {
            context.getRequest().setAttribute(SecurityContext.class.getName(), context);
            doFilter(context, filterChain);
        } catch (Exception ex) {
            log.warn("Error occurs when executing security filter, redirect to index", ex);
            context.redirect("index.shtml");
        } finally {
            if (request.getAttribute(SecurityContext.class.getName()) != null) {
                request.removeAttribute(SecurityContext.class.getName());
            }
        }

    }

    @Override
    public void destroy() {
    }

    private void doFilter(SecurityContext context, FilterChain filterChain) throws IOException, ServletException {
        String servletPath = context.getRequest().getServletPath();
        if (servletPath.equals("")) {
            log.warn("getServletPath returns empty path");
            context.redirect("index.shtml");
            return;
        }
        if (servletPath.contains("//") || servletPath.contains("..")) {
            log.warn("getServletPath returns malformed path: {}", servletPath);
            context.redirect("index.shtml");
            return;
        }

        if (StringUtils.startsWith(servletPath, "/")) {
            servletPath = StringUtils.substringAfter(servletPath, "/");
        }

        String method = context.getRequest().getMethod();
        if (StringUtils.equals(method, "POST") && StringUtils.equals(servletPath, "signin.shtml")) {
            // handle with signin requestion
            String username = StringUtils.defaultString(context.getRequest().getParameter("username"));
            String password = StringUtils.defaultString(context.getRequest().getParameter("password"));

            User user = userLoader.loadByUsername(username);
            if (user == null || !UserPassword.newInstance(user).match(password)) {
                // authentication failed
                context.redirect("signin.shtml");
                return;
            }
            context.setUser(user);

            SecurityCookie cookie = new SecurityCookie();
            cookie.setUsername(user.getUsername());
            cookie.setUserType(user.getUserType());
            cookie.save(context.getCookieManager());
            switch (user.getUserType()) {
                case PLAYER: {
                    context.redirect("home/main.shtml");
                    break;
                }
                case ADMINISTRATOR: {
                    context.redirect("admin/main.shtml");
                    break;
                }
                default: {
                    throw new UnsupportedOperationException();
                }
            }
            return;
        }

        // do permission checks
        boolean accessProtectedUrl = false;
        SecurityCookie cookie = SecurityCookie.load(context.getCookieManager());
        for (String path : permissions.keySet()) {
            if (!StringUtils.startsWith(servletPath, path)) {
                continue;
            }
            accessProtectedUrl = true;
            Set<UserType> permission = permissions.get(path);
            if (StringUtils.isBlank(cookie.getUsername()) || !permission.contains(cookie.getUserType())) {
                // no permission to access url
                context.redirect("signin.shtml");
                return;
            }
        }
        if (accessProtectedUrl) {
            User user = userLoader.loadByUsername(cookie.getUsername());
            if (user == null || user.getUserType() != cookie.getUserType()) {
                context.redirect("signin.shtml");
                return;
            }
            context.setUser(user);
        }

        filterChain.doFilter(context.getRequest(), context.getResponse());
    }
}
